import { WatchOptions } from "node:fs";
import { defineCommand, runMain as _runMain, ParsedArgs, ArgsDef } from "citty";
import { isAbsolute } from "pathe";
import { name, description, version } from "../package.json";
import { listen } from "./listen";
import { listenAndWatch, DevServerOptions, createDevServer } from "./server";
import type { HTTPSOptions, ListenOptions } from "./types";

export const main = defineCommand({
  meta: {
    name,
    description,
    version,
  },
  args: {
    cwd: {
      type: "string",
      description: "Current working directory",
    },
    entry: {
      type: "positional",
      description: "Listener entry file (./app.ts)",
      required: true,
    },
    name: {
      type: "string",
      description: "Name to use in the banner",
    },
    baseURL: {
      type: "string",
      description: "Base URL to use",
    },
    watch: {
      type: "boolean",
      description: "Watch for changes",
      alias: "w",
    },
    ws: {
      type: "boolean",
      description: "Enable Experimental WebSocket support",
    },
    ...getArgs(),
  },
  async run({ args }) {
    const opts: Partial<ListenOptions & WatchOptions & DevServerOptions> = {
      ...args,
      ...parseArgs(args),
      baseURL: args.baseURL,
      name: args.name,
    };

    const entry =
      isAbsolute(args.entry) || args.entry.startsWith(".")
        ? args.entry
        : `./${args.entry}`;

    if (args.watch) {
      await listenAndWatch(entry, opts);
    } else {
      const devServer = await createDevServer(entry, opts);
      await listen(devServer.nodeListener, {
        ...opts,
        _entry: devServer._entry,
        ws: opts.ws ? devServer._ws : undefined,
      });
      await devServer.reload(true);
    }
  },
});

export const runMain = () => _runMain(main);

/** Returns unjs/citty compatible args object */
export function getArgs() {
  return {
    port: {
      type: "string",
      description:
        "Port to listen on (use `PORT` environment variable to override)",
    },
    host: {
      description:
        "Host to listen on. If no value or an empty string provided, will listen on all available interfaces (use `HOST` environment variable to override)",
    },
    clipboard: {
      type: "boolean",
      description: "Copy the URL to the clipboard",
    },
    open: {
      type: "boolean",
      description: "Open the URL in the browser",
    },
    https: {
      type: "boolean",
      description: "Enable HTTPS",
    },
    "https.cert": {
      type: "string",
      description: "Path to TLS certificate used with HTTPS in PEM format",
    },
    "https.key": {
      type: "string",
      description: "Path to TLS key used with HTTPS in PEM format",
    },
    "https.pfx": {
      type: "string",
      description:
        "Path to PKCS#12 (.p12/.pfx) keystore containing a TLS certificate and Key",
    },
    "https.passphrase": {
      type: "string",
      description: "Passphrase used for TLS key or keystore",
    },
    "https.validityDays": {
      type: "string",
      description:
        "Validity in days of the autogenerated TLS certificate (https: true)",
    },
    "https.domains": {
      type: "string",
      description:
        "Comma separated list of domains and IPs, the autogenerated certificate should be valid for (https: true)",
    },
    publicURL: {
      type: "string",
      description: "Displayed public URL (used for QR code)",
      required: false,
    },
    qr: {
      type: "boolean",
      description: "Display The QR code of public URL when available",
      required: false,
    },
    public: {
      type: "boolean",
      description: "Listen to all network interfaces",
      required: false,
    },
    tunnel: {
      type: "boolean",
      description: "Open a tunnel using https://github.com/unjs/untun",
      required: false,
    },
  } as const satisfies ArgsDef;
}

type ParsedListhenArgs = ParsedArgs<ReturnType<typeof getArgs>>;

/** Convert unjs/citty compatible args to listhen options */
export function parseArgs(args: ParsedListhenArgs): Partial<ListenOptions> {
  return {
    port: args.port,
    // prettier-ignore
    hostname: typeof args.host === "string" ? args.host : (args.host === true ? "" : undefined),
    clipboard: args.clipboard,
    open: args.open,
    qr: args.qr,
    publicURL: args.publicURL,
    public: args.public,
    tunnel: args.tunnel,
    https: args.https
      ? <HTTPSOptions>{
          cert: args["https.cert"],
          key: args["https.key"],
          pfx: args["https.pfx"],
          passphrase: args["https.passphrase"],
          validityDays: args["https.validityDays"]
            ? +args["https.validityDays"]
            : undefined,
          domains: args["https.domains"]
            ? args["https.domains"].split(",")
            : undefined,
        }
      : false,
  };
}
